﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Mail;
using System.Threading;
using System.Threading.Tasks;
using System.Security;
using System.IO;
using System.Net.Mime;

namespace SMTP.Test.Client.Requests
{
    
    class MailTest
    {
        //event to indicate we've finished sending email
        public event EventHandler<MailingResults> MailSent;

        //variables to be used by mail client
        public string Server { get; set; }
        public string From { get; set; }
        public string To { get; set; }
        public string Subject { get; set; }
        public string Body { get; set; }
        public string Attachment { get; set; }
        public int Port { get; set; }
        public int Timeout { get; private set; }

        //secure email settings
        public string User { get; set; }
        public string Pass { get; set; }
        public bool SendNetworkCredentials { get; set; }
        public bool SendSSL { get; set; }
        

        //mail client
        SmtpClient mailClient;

        //constants
        const int DEFAULT_MAIL_CLIENT_TIMEOUT = 100; //10 seconds
        const int DEFAULT_PORT = 25;

        //cancellation token
        CancellationTokenSource _sourceToken;

        public MailTest(string server)
        {
            Server = server;
            Port = DEFAULT_PORT;
            Timeout = DEFAULT_MAIL_CLIENT_TIMEOUT;
            User = "";
            SendSSL = false;
        }

        public MailTest(string server, int port = DEFAULT_PORT, int timeout = DEFAULT_MAIL_CLIENT_TIMEOUT)
        {
            Server = server;
            Port = port;
            Timeout = timeout;
            User = string.Empty;
            Pass = string.Empty;
            SendNetworkCredentials = false;
            SendSSL = false;
        }

        public MailTest(string server, string from, string to, string subject, string body, string attachment = "", int port = DEFAULT_PORT, int timeout = DEFAULT_MAIL_CLIENT_TIMEOUT)
        {
            Server = server;
            From = from;
            To = to;
            Subject = subject;
            Body = body;
            Attachment = attachment;
            Port = port;
            Timeout = timeout;
            User = string.Empty;
            Pass = string.Empty;
            SendNetworkCredentials = false;
            SendSSL = false;
        }

        public MailTest(string server, string from, string to, string subject, string body, string attachment = "", string user = "",  string pass = "", bool sendnetworkcredentials = false, bool sendssl = false, int port = DEFAULT_PORT, int timeout = DEFAULT_MAIL_CLIENT_TIMEOUT)
        {
            Server = server;
            From = from;
            To = to;
            Subject = subject;
            Body = body;
            Attachment = attachment;
            Port = port;
            Timeout = timeout;
            User = user;
            Pass = pass;
            SendNetworkCredentials = sendnetworkcredentials;
            SendSSL = sendssl;
        }

        void mailClient_SendCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
        {
            if (e.Cancelled)
            {
                OnMailSent(false, "Cancelled sending of email");
            }
            else if (e.Error != null)
            {
                string err = GetErrorMessage(e.Error.Message, e.Error.InnerException);
                OnMailSent(false, err);
            }
            else
            {
                OnMailSent(true, "Mail successfully sent to " + Server);
            }
        }

        public void SendMail()
        {
            _sourceToken = new CancellationTokenSource();

            Task mailTask = new Task(() =>
            {
                FileStream fs = null;
                try
                {
                    if (Server.ScanPort(Port))
                    {
                        if (!_sourceToken.Token.IsCancellationRequested)
                        {
                            MailMessage mailMsg = new MailMessage(From, To, Subject, Body);
                            mailClient = new SmtpClient(Server, Port);
                            mailClient.Timeout = Timeout;

                            
                            if (!string.IsNullOrEmpty(Attachment))
                            {
                                //old stuff used for testing viruses at schwab that needs removed
                                //fs = new FileStream(Attachment, FileMode.Open, FileAccess.Read);
                                //mailMsg.Attachments.Add(new Attachment(fs, "EICAR", MediaTypeNames.Application.Octet));
                                mailMsg.Attachments.Add(new Attachment(Attachment));
                                
                            }

                            //send ssl secured email
                            if (SendNetworkCredentials)
                            {
                                mailClient.DeliveryMethod = SmtpDeliveryMethod.Network;
                                mailClient.UseDefaultCredentials = false;
                                mailClient.Credentials = new System.Net.NetworkCredential(User, Pass);
                                mailClient.EnableSsl = SendSSL;
                            }

                            mailClient.SendCompleted += new SendCompletedEventHandler(mailClient_SendCompleted);
                            mailClient.SendAsync(mailMsg, "_");
                        }
                        else
                        {
                            _sourceToken.Token.ThrowIfCancellationRequested();
                            OnMailSent(false, "Cancelled sending of email");
                        }
                    }
                    else
                    {
                        if (_sourceToken.Token.IsCancellationRequested)
                        {
                            _sourceToken.Token.ThrowIfCancellationRequested();
                        }
                        else
                        {
                            OnMailSent(false, "Unable to connect to server " + Server + " on port " + Port.ToString());
                        }
                    }
                }
                catch (SmtpFailedRecipientsException ex)
                {
                    string err = GetErrorMessage("Failed to send to recipeints: " + ex.Message, ex.InnerException);
                    OnMailSent(false, err);
                }
                catch (SmtpFailedRecipientException ex)
                {
                    string err = GetErrorMessage("Failed to send to recipeint: " + ex.Message, ex.InnerException);
                    OnMailSent(false, err);
                }
                catch (SmtpException ex)
                {
                    string err = GetErrorMessage("An SMTP exception occurred: " + ex.Message, ex.InnerException);
                    OnMailSent(false, err);
                }
                catch (AggregateException ex)
                {
                    string err = GetErrorMessage(ex.Message, ex.InnerException);
                    OnMailSent(false, err);
                }
                catch (Exception ex)
                {
                    string err = GetErrorMessage(ex.Message, ex.InnerException);
                    OnMailSent(false, err);
                }
            }, _sourceToken.Token);

            _sourceToken.Token.Register(() =>
            {
                if (mailClient != null)
                {
                    mailClient.SendAsyncCancel();
                }
            });

            mailTask.Start();

        }

        private string GetErrorMessage(string baseMsg, Exception innerEx)
        {
            string err = baseMsg;
            Exception ex = innerEx;

            while (ex != null)
            {
                err += "\r\n" + ex.Message;
                ex = ex.InnerException;
            }

            return err;
        }

        //no longer using async method because of issues with background worker
        public void Cancel()
        {
            //mailClient.SendAsyncCancel();
            try
            {
                if (_sourceToken.Token.CanBeCanceled)
                {
                    _sourceToken.Cancel();
                }
            }
            catch (AggregateException ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
        
        private void OnMailSent(bool success, string results)
        {
            var handler = MailSent;
            if(handler != null) MailSent(this, new MailingResults(success, results));
        }
    }

    public class MailingResults : EventArgs
    {
        public bool Success { get; private set; }
        public string Results { get; private set; }

        public MailingResults(bool success, string results)
        {
            Success = success;
            Results = results;
        }
    }
}